﻿using System;
using System.Collections.Generic;
using System.Linq;
using SlamCms.Common;

namespace SlamCms.Data
{
	internal class TagTreeBuilder
	{
		private readonly DataContext _dataContext;
		private readonly IEnumerable<TagTreeInternal> tagTreeInternals; 
		private readonly IEnumerable<TagHierarchyPosition> tagHierarchyPositions;
		private readonly Dictionary<string, Tag> tagDictionary; 

		public TagTreeBuilder(DataContext dataContext)
		{
			this._dataContext = dataContext;
			this.tagTreeInternals = this.GetTagTrees();
			this.tagHierarchyPositions = dataContext.Database.GetTagHierarchy();
			this.tagDictionary = dataContext.Tags.ToDictionary(t => t.TagId);
		}

		public IEnumerable<TagTree> BuildAll()
		{
			var result = new List<TagTree>();
			foreach (var tagTreeIn in this.tagTreeInternals)
			{
				var tagTree = Build(tagTreeIn);
				result.Add(tagTree);
			}

			return result;
		}

		public TagTree Build(string tagTreeId)
		{
			var tagTreeIn = this.GetTagTrees()
							  .Where(t => t.TagTreeId == tagTreeId)
							  .FirstOrDefault();

			if (tagTreeIn == null)
				throw new Exception("Could not find the tag tree id {0}".F(tagTreeId));

			return Build(tagTreeIn);
		}

		private TagTree Build(TagTreeInternal tagTreeInternal)
		{
			var tagTree = new TagTree();
			tagTree.TagTreeId = tagTreeInternal.TagTreeId;
			tagTree.Title = tagTreeInternal.Title;
			tagTree.SortOrder = tagTreeInternal.SortOrder;

			var rootNode = new TagTreeNode()
			{
				Id = "root",
				Text = "root",
				Type = TagTreeNodeType.Root
			};

			tagTree.Root = rootNode;

			var tagHierarchy = this.tagHierarchyPositions.Where(t => t.TagTreeId == tagTree.TagTreeId);

			this.AddTagCategoryNodes(tagTree, tagHierarchy);

			this.AddTagHierarchyNodes(tagTree, tagHierarchy);

			return tagTree;
		}

		private void AddTagCategoryNodes(TagTree tagTree, IEnumerable<TagHierarchyPosition> tagHierarchy)
		{
			// Only tag categories 
			var tagCategories = _dataContext.TagCategories
										   .Where(tc => tagHierarchy.Any(x => x.TagCategoryId == tc.TagCategoryId));

			foreach (var tagCategory in tagCategories)
			{
				var node = new TagTreeNode()
				{
					Id = tagCategory.TagCategoryId,
					Type = TagTreeNodeType.TagCategory,
					Text = tagCategory.Title,
					TagCategory = tagCategory,
					Depth = 1
				};

				tagTree.Root.Children.Add(node);
			}
		}

		private void AddTagHierarchyNodes(TagTree tagTree, IEnumerable<TagHierarchyPosition> tagHierarchy)
		{
			Tag tag;
			TagTreeNode parentNode = null;
			
			foreach (var position in tagHierarchy)
			{
				if (!tagDictionary.TryGetValue(position.TagId, out tag))
					continue;

				if (!position.ParentPositionId.HasValue)
				{
					// top level tag
					parentNode = tagTree.Root.Children.FirstOrDefault(n => n.TagCategory.TagCategoryId == position.TagCategoryId);
					if (parentNode == null)
						throw new Exception("Node not found for tag category {0}".F(position.TagCategoryId));
				}
				else
				{
					parentNode = tagTree.FindNode("p{0}".F(position.ParentPositionId));
					if (parentNode == null)
						throw new Exception("Parent node not found with id p{0}".F(position.ParentPositionId));
				}

				var node = new TagTreeNode()
				{
					Id = "p{0}".F(position.PositionId),
					Parent = parentNode,
					Type = TagTreeNodeType.Tag,
					Text = tag.DisplayName,
					Identifier = tag.Identifier,
					Tag = tag,
					TagCategory = parentNode.TagCategory,
					Depth = parentNode.Depth + 1
				};

				node.Identifiers = this.GetIdentifiers(node);

				parentNode.Children.Add(node);
			}
		}

		private string[] GetIdentifiers(TagTreeNode node)
		{ 
			var identifiers = new List<string>();

			while (node != null)
			{
				if (node.Type == TagTreeNodeType.Tag)
					identifiers.Add(node.Identifier);

				node = node.Parent;
			}

			identifiers.Reverse();

			return identifiers.ToArray();
		}

		private IEnumerable<TagTreeInternal> GetTagTrees()
		{
			return _dataContext.Database.RetrieveAll<TagTreeInternal>("TagTree", "SortOrder");
		}

		private class TagTreeInternal
		{
			public string TagTreeId { get; set; }
			public Guid ListId { get; set; }
			public string Title { get; set; }
			public int SortOrder { get; set; }
		}
	}
}
